﻿using System;
using System.Data.Common;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Reflection;
using System.Reflection.Emit;
using MvcMiniProfiler.Data;

#pragma warning disable 1591 // xml doc comments warnings

#if LINQ_TO_SQL
namespace System.Data.Linq
{
    public static class DataContextUtils
    {

        public static T CreateDataContext<T>(this DbConnection connection) where T : System.Data.Linq.DataContext
        {
            return CtorCache<T, IDbConnection>.Ctor(connection);
        }
    }
}
#endif
#if ENTITY_FRAMEWORK
namespace System.Data.Objects
{


    public static class ObjectContextUtils
    {
        static class MetadataCache<U> where U : System.Data.Objects.ObjectContext
        {
            public static System.Data.Metadata.Edm.MetadataWorkspace workspace;

            static MetadataCache()
            {
                workspace = new System.Data.Metadata.Edm.MetadataWorkspace(
                  new string[] { "res://*/" },
                  new Assembly[] { typeof(U).Assembly });
            }
        }

        /// <summary>
        /// Creates an <see cref="ObjectContext"/> for the specified connection string.
        /// </summary>
        /// <remarks>
        /// Creates an instance of Objectcontext that uses the <see cref="MvcMiniProfiler.Data.ProfiledDbConnection"/>,
        /// especially useful for EF POCO situations.
        /// Some of the code has been cribbed from this StackOverflow answer:
        /// http://stackoverflow.com/questions/6296444/using-mvc-mini-profiler-with-ef-4-0-and-ninject
        /// </remarks>
        /// <param name="connectionString">The connection string to create the object context for.</param>
        /// <param name="metadataAssembly">A reference to the assembly that contains the EF metadata.</param>
        /// <returns>An <see cref="ObjectContext"/> object.</returns>
        public static ObjectContext CreateObjectContext(string connectionString, Assembly metadataAssembly)
        {
            // Make the entity framework connection string easier to deal with
            EntityConnectionStringBuilder builder = new EntityConnectionStringBuilder(connectionString);

            // Find the proper factory for the underlying connection.
            DbProviderFactory factory = DbProviderFactories.GetFactory(builder.Provider);

            // Build the new connection.
            DbConnection connection = null, tempConnection = null;
            try
            {
                tempConnection = factory.CreateConnection();
                tempConnection.ConnectionString = builder.ProviderConnectionString;

                connection = tempConnection;
                tempConnection = null;
            }
            finally
            {
                // If creating of the connection failed, dispose the connection.
                if (tempConnection != null)
                {
                    tempConnection.Dispose();
                }
            }

            // Create the metadata workspace
            MetadataWorkspace workspace = new MetadataWorkspace(builder.Metadata.Split('|'), new Assembly[] { metadataAssembly });

            return CreateObjectContext<ObjectContext>(ProfiledDbConnection.Get(connection), workspace);
        }

        public static T CreateObjectContext<T>(this DbConnection connection) where T : System.Data.Objects.ObjectContext
        {
            var workspace = MetadataCache<T>.workspace;
            return CreateObjectContext<T>(connection, workspace);
        }

        public static T CreateObjectContext<T>(this DbConnection connection, MetadataWorkspace workspace) where T : System.Data.Objects.ObjectContext
        {
            var factory = DbProviderServices.GetProviderFactory(connection);

            var itemCollection = workspace.GetItemCollection(System.Data.Metadata.Edm.DataSpace.SSpace);
            itemCollection.GetType().GetField("_providerFactory", // <==== big fat ugly hack
                    BindingFlags.NonPublic | BindingFlags.Instance).SetValue(itemCollection, factory);

            var ec = new System.Data.EntityClient.EntityConnection(workspace, connection);
            return CtorCache<T, System.Data.EntityClient.EntityConnection>.Ctor(ec);
        }
    }
}
#endif
#if LINQ_TO_SQL || ENTITY_FRAMEWORK
namespace MvcMiniProfiler.Data
{
    internal static class CtorCache<TType, TArg> where TType : class
    {
        public static readonly Func<TArg, TType> Ctor;
        static CtorCache()
        {
            Type[] argTypes = new Type[] { typeof(TArg) };
            var ctor = typeof(TType).GetConstructor(argTypes);
            if (ctor == null)
            {
                Ctor = x => { throw new InvalidOperationException("No suitable constructor defined"); };
            }
            else
            {
                var dm = new DynamicMethod("ctor", typeof(TType), argTypes);
                var il = dm.GetILGenerator();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Newobj, ctor);
                il.Emit(OpCodes.Ret);
                Ctor = (Func<TArg, TType>)dm.CreateDelegate(typeof(Func<TArg, TType>));
            }
        }
    }
}
#endif

#pragma warning restore 1591 // xml doc comments warnings